<template>
  <div :id="id" ref="container" class="draggable-resizable" :style="containerStyle">
    <div class="header" @mousedown.prevent="onMouseDown" :style="{ cursor: mouseCursor }">
      {{ id }}
    </div>

    <div
      v-for="dir in directions"
      :key="dir"
      :class="['resize-handle', dir]"
      @mousedown.stop.prevent="onResizeHandleMouseDown(dir, $event)"
    ></div>
  </div>
</template>

<script setup lang="ts" name="DraggableResizable">
import type { PropType } from 'vue';
import { ref, reactive, defineProps, defineEmits } from 'vue';

const props = defineProps({
  id: {
    type: String,
    required: true,
  },
  x: {
    type: Number,
    required: true,
  },
  y: {
    type: Number,
    required: true,
  },
  width: {
    type: Number,
    required: true,
  },
  height: {
    type: Number,
    required: true,
  },
  zIndex: {
    type: String,
    default: '1',
    required: true,
  },
  snapDistance: {
    type: Number,
    default: 20,
  },
  onDrag: {
    type: Function,
    required: true,
  },
  onResize: {
    type: Function,
    required: true,
  },
  detectCollision: {
    type: Function,
    required: true,
  },
  detectSnap: {
    type: Function,
    required: true,
  },
  checkClosestComponent: {
    type: Function,
    required: true,
  },
  setCurrentComponent: {
    type: Function,
    required: true,
  },
  handleCollision: {
    type: Function,
    required: true,
  },
  directions: {
    type: Array as PropType<string[]>,
    required: false,
    default: ['top', 'bottom', 'left', 'right', 'top-left', 'top-right', 'bottom-left', 'bottom-right'],
  },
});

const emit = defineEmits(['drag', 'resize']);
const mouseCursor = ref('grab');

const isCollied = ref(false);
const isSnap = ref(false);
const startX = ref(0);
const startY = ref(0);
const startLeft = ref(0);
const startTop = ref(0);

const container = ref<HTMLElement | null>(null);
const ghost = ref<HTMLElement | null>(null);

const containerStyle = reactive({
  id: `${props.id}`,
  width: `${props.width}px`,
  height: `${props.height}px`,
  top: `${props.y}px`,
  left: `${props.x}px`,
  zIndex: props.zIndex,
  transition: 'none',
});

const updatePosition = (left: number, top: number, snap = false) => {
  isSnap.value = snap;
  if (isSnap.value) {
    containerStyle.transition = 'left 0.08s ease-out, top 0.08s ease-out';
  } else {
    containerStyle.transition = 'none';
  }
  containerStyle.left = `${left}px`;
  containerStyle.top = `${top}px`;
};

const updateGhostPosition = (left: number, top: number) => {
  if (ghost.value) {
    ghost.value.style.left = `${left}px`;
    ghost.value.style.top = `${top}px`;
  }
};

const updateSize = (width: number, height: number) => {
  containerStyle.width = `${width}px`;
  containerStyle.height = `${height}px`;
};

const onMouseDown = (event: MouseEvent) => {
  mouseCursor.value = 'grabbing';
  startX.value = event.clientX;
  startY.value = event.clientY;
  startLeft.value = parseInt(containerStyle.left);
  startTop.value = parseInt(containerStyle.top);
  props.setCurrentComponent(containerStyle);
  containerStyle.zIndex = '3';

  ghost.value = document.createElement('div');
  ghost.value.style.width = containerStyle.width;
  ghost.value.style.height = containerStyle.height;
  ghost.value.style.backgroundColor = '#E33B54';
  ghost.value.style.zIndex = '1';
  ghost.value.style.position = 'absolute';
  ghost.value.style.left = containerStyle.left;
  ghost.value.style.top = containerStyle.top;
  const fatherEle = document.getElementById(props.id);
  fatherEle?.appendChild(ghost.value);

  const onMouseMove = (moveEvent: MouseEvent) => {
    //* 使用 requestAnimationFrame 来优化性能
    requestAnimationFrame(() => {
      const newLeft = startLeft.value + (moveEvent.clientX - startX.value);
      const newTop = startTop.value + (moveEvent.clientY - startY.value);
      updateGhostPosition(newLeft, newTop);
      // const closestComponent = props.checkClosestComponent(
      //   props.id,
      //   newLeft,
      //   newTop,
      //   parseInt(containerStyle.width),
      //   parseInt(containerStyle.height),
      // );
      //* console.log('closestComponent', closestComponent);
      updatePosition(newLeft, newTop, false);
    });
  };

  const onMouseUp = (moveEvent: MouseEvent) => {
    moveAndResize(moveEvent);
    mouseCursor.value = 'grab';
    document.removeEventListener('mousemove', onMouseMove);
    document.removeEventListener('mouseup', onMouseUp);
    if (isCollied.value) {
      containerStyle.zIndex = '3';
    } else {
      containerStyle.zIndex = '1';
      isCollied.value = false;
    }
    containerStyle.transition = 'none';
    console.log('containerStyle', containerStyle);
  };

  document.addEventListener('mousemove', onMouseMove);
  document.addEventListener('mouseup', onMouseUp);
  ghost.value.parentNode?.removeChild(ghost.value);
};

const moveAndResize = (moveEvent: MouseEvent) => {
  const newLeft = startLeft.value + (moveEvent.clientX - startX.value);
  const newTop = startTop.value + (moveEvent.clientY - startY.value);
  console.log(newLeft);
  console.log(newTop);

  // containerStyle.zIndex = '3';

  //* 使用 requestAnimationFrame 来优化性能
  requestAnimationFrame(() => {
    //* 检测吸附
    const snapResult = props.detectSnap(
      props.id,
      newLeft,
      newTop,
      parseInt(containerStyle.width),
      parseInt(containerStyle.height),
      props.snapDistance,
    );
    let finalLeft = snapResult.left;
    let finalTop = snapResult.top;
    isSnap.value = snapResult.isSnap;
    console.log('finalLeft', finalLeft);
    console.log('finalTop', finalTop);

    const colliedInfo = props.detectCollision(
      props.id,
      finalLeft,
      finalTop,
      parseInt(containerStyle.width),
      parseInt(containerStyle.height),
    );

    isCollied.value = colliedInfo.value;

    updatePosition(finalLeft, finalTop, isSnap.value);
    emit('drag', props.id, parseInt(containerStyle.left), parseInt(containerStyle.top));

    //* 检测边界和碰撞
    //todo
    //* if (!colliedInfo.value) {
    //*   updatePosition(finalLeft, finalTop, isSnap);
    //* } else {
    //*   const colliedComponent = colliedInfo.colliedComponent;
    //*   const collidedDirection = colliedInfo.collidedDirection;
    //*   if (colliedComponent && collidedDirection) {
    //*     //* 左右两边碰撞，则只移动垂直方向
    //*     if (
    //*       collidedDirection === 'left' ||
    //*       (collidedDirection === 'right' &&
    //*         (parseInt(containerStyle.height) + finalTop >= colliedComponent.y ||
    //*           finalTop <= colliedComponent.y + parseInt(colliedComponent.height)))
    //*     ) {
    //*       containerStyle.top = `${finalTop}px`;
    //*     }
    //*     //* 上下两边碰撞，则只移动水平方向
    //*     if (
    //*       collidedDirection === 'top' ||
    //*       (collidedDirection === 'bottom' &&
    //*         (parseInt(containerStyle.width) + finalLeft >= colliedComponent.x ||
    //*           finalLeft <= colliedComponent.x + parseInt(colliedComponent.width)))
    //*     ) {
    //*       containerStyle.left = `${finalLeft}px`;
    //*     }
    //*   }
    //* }
  });
};

/**
 * 组件resize事件
 * @param dir
 * @param event
 */
const onResizeHandleMouseDown = (dir: string, event: MouseEvent) => {
  const startX = event.clientX;
  const startY = event.clientY;
  const startWidth = parseInt(containerStyle.width);
  const startHeight = parseInt(containerStyle.height);
  const startLeft = parseInt(containerStyle.left);
  const startTop = parseInt(containerStyle.top);

  const onMouseMove = (moveEvent: MouseEvent) => {
    let newWidth = startWidth;
    let newHeight = startHeight;
    let newLeft = startLeft;
    let newTop = startTop;

    if (dir.includes('right')) {
      newWidth = startWidth + (moveEvent.clientX - startX);
    } else if (dir.includes('left')) {
      newWidth = startWidth - (moveEvent.clientX - startX);
      newLeft = startLeft + (moveEvent.clientX - startX);
    }

    if (dir.includes('bottom')) {
      newHeight = startHeight + (moveEvent.clientY - startY);
    } else if (dir.includes('top')) {
      newHeight = startHeight - (moveEvent.clientY - startY);
      newTop = startTop + (moveEvent.clientY - startY);
    }

    //* 使用 requestAnimationFrame 来优化性能
    requestAnimationFrame(() => {
      //* 检测吸附
      const snapResult = props.detectSnap(props.id, newLeft, newTop, newWidth, newHeight, props.snapDistance);
      let finalWidth = snapResult.width;
      let finalHeight = snapResult.height;
      let finalLeft = snapResult.left;
      let finalTop = snapResult.top;
      isSnap.value = snapResult.isSnap;

      updateSize(finalWidth, finalHeight);
      updatePosition(finalLeft, finalTop, isSnap.value);

      return;

      const colliedInfo = props.detectCollision(props.id, finalLeft, finalTop, finalWidth, finalHeight);
      //* 检测碰撞
      if (!colliedInfo.value) {
        updateSize(finalWidth, finalHeight);
        updatePosition(finalLeft, finalTop, isSnap.value);
      } else {
        //todo 当元素的某一边已经吸附，且resize的方向并非和吸附的方向一致，则仍可以进行updateSize
        const colliedComponent = colliedInfo.colliedComponent;
        const collidedDirection = colliedInfo.collidedDirection;
        if (colliedComponent && collidedDirection) {
          //* 左右两边碰撞，则只移动垂直方向
          if (
            collidedDirection === 'left' ||
            (collidedDirection === 'right' &&
              (parseInt(containerStyle.height) + finalTop >= colliedComponent.y ||
                finalTop <= colliedComponent.y + parseInt(colliedComponent.height)))
          ) {
            containerStyle.top = `${finalTop}px`;
          }
          //* 上下两边碰撞，则只移动水平方向
          if (
            collidedDirection === 'top' ||
            (collidedDirection === 'bottom' &&
              (parseInt(containerStyle.width) + finalLeft >= colliedComponent.x ||
                finalLeft <= colliedComponent.x + parseInt(colliedComponent.width)))
          ) {
            containerStyle.left = `${finalLeft}px`;
          }
        }
      }
    });
  };

  const onMouseUp = () => {
    document.removeEventListener('mousemove', onMouseMove);
    document.removeEventListener('mouseup', onMouseUp);
    emit('resize', props.id, parseInt(containerStyle.width), parseInt(containerStyle.height));
  };

  document.addEventListener('mousemove', onMouseMove);
  document.addEventListener('mouseup', onMouseUp);
};

//* 使用 ResizeObserver 来监听容器大小变化
const resizeObserver = new ResizeObserver((entries) => {
  console.log(`entries, ${entries}`);
  const entry = entries[0];
  const { width, height } = entry.contentRect;
  updateSize(width, height);
});

//* 在容器元素存在时，才监听其大小变化
if (container.value) {
  resizeObserver.observe(container.value);
}
</script>

<style lang="less" scoped>
.draggable-resizable {
  position: absolute;
  background-color: lightblue;
  border: 1px solid #333;
  box-sizing: border-box;
  .header {
    width: 100%;
    background-color: #333;
    height: 40px;
    border-bottom: 1px solid #333;
    color: #fff;
  }
}
.resize-handle {
  position: absolute;
  width: 10px;
  height: 10px;
  background-color: #333;
  z-index: 1;
}
.resize-handle.top {
  top: -5px;
  left: 50%;
  transform: translateX(-50%);
  cursor: n-resize;
}
.resize-handle.bottom {
  bottom: -5px;
  left: 50%;
  transform: translateX(-50%);
  cursor: s-resize;
}
.resize-handle.left {
  left: -5px;
  top: 50%;
  transform: translateY(-50%);
  cursor: w-resize;
}
.resize-handle.right {
  right: -5px;
  top: 50%;
  transform: translateY(-50%);
  cursor: e-resize;
}
.resize-handle.top-left {
  top: -5px;
  left: -5px;
  cursor: nw-resize;
}
.resize-handle.top-right {
  top: -5px;
  right: -5px;
  cursor: ne-resize;
}
.resize-handle.bottom-left {
  bottom: -5px;
  left: -5px;
  cursor: sw-resize;
}
.resize-handle.bottom-right {
  bottom: -5px;
  right: -5px;
  cursor: se-resize;
}
</style>
